package com.feng.zhihao.apigeteway.controller;

import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.feng.zhihao.apigeteway.cache.CacheManager;
import com.feng.zhihao.apigeteway.cache.CacheUtil;
import com.feng.zhihao.apigeteway.cache.impl.MemoryCache;
import com.feng.zhihao.apigeteway.dao.interfaces.InterfaceInfoDao;
import com.feng.zhihao.apigeteway.dao.interfaces.ProjectInfoDao;
import com.feng.zhihao.apigeteway.dao.interfaces.ReqInfoFlowDao;
import com.feng.zhihao.apigeteway.dao.user.ChannelInfoDao;
import com.feng.zhihao.apigeteway.dao.user.ChannelInterfaceCaseDao;
import com.feng.zhihao.apigeteway.dao.user.ChannelInterfaceDao;
import com.feng.zhihao.apigeteway.dao.user.ChannelProjectDao;
import com.feng.zhihao.apigeteway.entity.ChannelInfo;
import com.feng.zhihao.apigeteway.entity.ChannelInterface;
import com.feng.zhihao.apigeteway.entity.ChannelInterfaceCase;
import com.feng.zhihao.apigeteway.entity.ChannelInterfaceCaseId;
import com.feng.zhihao.apigeteway.entity.ChannelInterfaceId;
import com.feng.zhihao.apigeteway.entity.ChannelProject;
import com.feng.zhihao.apigeteway.entity.ChannelProjectId;
import com.feng.zhihao.apigeteway.entity.InterfaceFieldInfo;
import com.feng.zhihao.apigeteway.entity.InterfaceInfo;
import com.feng.zhihao.apigeteway.entity.ProjectInfo;
import com.feng.zhihao.apigeteway.entity.ReqInfoFlow;
import com.feng.zhihao.apigeteway.exception.ExceptionMessage;
import com.feng.zhihao.apigeteway.gateway.ReqContext;
import com.feng.zhihao.apigeteway.gateway.bean.REQ_PARAMS_POSITION;
import com.feng.zhihao.apigeteway.gateway.bean.RES_PARAMS_POSITION;
import com.feng.zhihao.apigeteway.util.BeanUtil;
import com.feng.zhihao.apigeteway.util.GenerationRuleUtil;
import com.feng.zhihao.apigeteway.util.ParamsUtil;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.LogFactory;
import com.xiaoleilu.hutool.util.StrUtil;

@Controller
@RequestMapping("/")
public class Gateway {
	public final static String projectInfoItem="projectInfoItem";
	Log log=LogFactory.get();
	@Autowired
	ProjectInfoDao projectInfoDao;
	@Autowired
	ChannelProjectDao channelProjectDao;
	@Autowired
	ChannelInfoDao channelInfoDao;
	@Autowired
	ReqInfoFlowDao reqInfoFlowDao;
	@Autowired
	ChannelInterfaceDao channelInterfaceDao;
	@Autowired
	ChannelInterfaceCaseDao channelInterfaceCaseDao;
	private PathMatcher pathMatcher = new AntPathMatcher();
	
	CacheManager cacheManager;
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    @Autowired
    InterfaceInfoDao interfaceInfoDao;
    
	public Gateway() {
		super();
		cacheManager=CacheUtil.createReloadCache("gateway", new MemoryCache(0,0), 1000, (cm)->{
			if(transactionManager == null){
				return;
			}
			TransactionStatus status=null;
			try {
				status = transactionManager.getTransaction(new DefaultTransactionDefinition());
				List<ProjectInfo> projectList = projectInfoDao.findAll();
				JSON.toJSONString(projectList);
				cm.put(projectInfoItem, projectList);
				
			} catch (Throwable e) {
				log.error(e);
			}finally {
				transactionManager.rollback(status);
			}
		});
	}


	@RequestMapping("/gw/**")
	@ResponseBody
	public void gateway(HttpServletRequest request,HttpServletResponse response ){
		String servletPath=request.getServletPath();
		String projectServletPath=servletPath.substring(4, servletPath.length());
		Optional<ProjectInfo> tmp = getProjectInfo(request, response, projectServletPath);
		ProjectInfo projectInfo=null;
		ReqInfoFlow infoFlow=new ReqInfoFlow(UUID.randomUUID().toString());
		infoFlow.setReqTimestamp(new Timestamp(System.currentTimeMillis()));
		if(tmp.isPresent()){
			projectInfo=tmp.get();
			projectInfo=projectInfoDao.getOne(projectInfo.getProjectId());
			infoFlow.setProjectId(projectInfo.getProjectId());
		}else{
			response.setStatus(com.xiaoleilu.hutool.http.HttpStatus.HTTP_NOT_FOUND);
			infoFlow.setReqSuccess(false);
			infoFlow.setReqBody(servletPath);
			infoFlow.setResCode("-1");
			infoFlow.setResMessage("未发现匹配项目");
			reqInfoFlowDao.save(infoFlow);
			return ;
		}

		//创建请求上下文信息
		ReqContext reqContext = createReqContext(request, response, projectInfo, projectServletPath);
		//设置请求参数
		setRequestParams(reqContext, request);
		//设置请求头
		setRequestHead(reqContext, request);
		
		try{
			//判断项目状态
			if(projectInfo.getProjectState() == 0){
				response.setStatus(com.xiaoleilu.hutool.http.HttpStatus.HTTP_NOT_FOUND);
				ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceCodeFieldId(),ExceptionMessage.PROJECT_SHUT_DOWN.getCode(), reqContext);
				ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceMessageFieldId(),StrUtil.format(ExceptionMessage.PROJECT_SHUT_DOWN.getMessage()), reqContext);
				return;
			}

			//设置接口参数
			setReqParams(reqContext, infoFlow, projectInfo);
			
			//获取渠道
			ChannelInfo channelInfo=getChannelInfo(reqContext, infoFlow, projectInfo);
			//获取渠道项目信息
			ChannelProject channelProject=null;
			if(channelInfo != null){
				channelProject=getChannelProject(reqContext, infoFlow, projectInfo, channelInfo);
				if(channelProject == null){
					ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceCodeFieldId(),ExceptionMessage.CHANNEL_HAVE_NO_RIGHT.getCode(), reqContext);
					ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceMessageFieldId(),StrUtil.format(ExceptionMessage.CHANNEL_HAVE_NO_RIGHT.getMessage(), channelInfo.getChannelId(), projectInfo.getProjectId()), reqContext);
					return ;
				}
			}
			
			
			//获取请求流水号
			Object flowId = ParamsUtil.getValue(projectInfo.getInterfaceFieldInfoByInterfaceFlowIdFieldId(), reqContext);
			infoFlow.setFlowId(flowId == null ? null : flowId.toString());
			
			//通过项目获取 接口
			InterfaceInfo interfaceInfo = getInterfaceInfo(reqContext, infoFlow, projectInfo);
			if(interfaceInfo == null){
				ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceCodeFieldId(),ExceptionMessage.INTERFACE_NOT_FOUND.getCode(), reqContext);
				ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceMessageFieldId(),StrUtil.format(ExceptionMessage.INTERFACE_NOT_FOUND.getMessage()), reqContext);
				return;
			}
			//获取如果在没有在参数其他位置获取到请求渠道当前位置在获取一次
			if(channelInfo == null){
				channelInfo=getChannelInfo(reqContext, infoFlow, projectInfo);
				if(channelInfo == null){
					ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceCodeFieldId(),ExceptionMessage.CHANNEL_NOT_FIND.getCode(), reqContext);
					ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceMessageFieldId(),StrUtil.format(ExceptionMessage.CHANNEL_NOT_FIND.getMessage(), ParamsUtil.getValue(projectInfo.getInterfaceFieldInfoByChannelFieldId(), reqContext).toString()), reqContext);
					return;
				}
				//获取渠道项目信息
				channelProject=getChannelProject(reqContext, infoFlow, projectInfo, channelInfo);
				if(channelProject == null){
					ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceCodeFieldId(),ExceptionMessage.CHANNEL_HAVE_NO_RIGHT.getCode(), reqContext);
					ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceMessageFieldId(),StrUtil.format(ExceptionMessage.CHANNEL_HAVE_NO_RIGHT.getMessage(), channelInfo.getChannelId(), projectInfo.getProjectId()), reqContext);
					return ;
				}
			}
			
			
			//参数验证
			String message=ParamsUtil.validator(interfaceInfo, reqContext);
			if(!StrUtil.isBlank(message)){
				ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceCodeFieldId(),ExceptionMessage.PARAMS_VALIDATOR_EXCEPTION.getCode(), reqContext);
				ParamsUtil.setValue(projectInfo.getInterfaceFieldInfoByInterfaceMessageFieldId(),StrUtil.format(ExceptionMessage.PARAMS_VALIDATOR_EXCEPTION.getMessage(), message), reqContext);
				return ;
			}
			//生成返回结果
			GenerationRuleUtil.generationResponse(interfaceInfo, reqContext);
		}finally {
			try {
				//设置返回值
				setResponse(response, reqContext);
				//记录请求信息
				saveInfoFlow(infoFlow, reqContext, projectInfo);
				//保存渠道接口调用记录
				saveChannelInterface(infoFlow, reqContext);
				//保存渠道接口案例信息
				saveInterfaceCase(reqContext);
			} catch (Exception e) {
				log.error(e);
			}
			
		}
		
	}
	private ChannelProject getChannelProject(ReqContext reqContext ,ReqInfoFlow infoFlow,ProjectInfo projectInfo,ChannelInfo channelInfo){
		ChannelProjectId channelProjectId=new ChannelProjectId(channelInfo.getChannelId(), projectInfo.getProjectId());
		ChannelProject channelProject=channelProjectDao.getOne(channelProjectId);
		if(channelProject == null){
			return null;
		}
		reqContext.setChannelProject(channelProject);
		return channelProject;
	}
	private ChannelInfo getChannelInfo(ReqContext reqContext ,ReqInfoFlow infoFlow,ProjectInfo projectInfo){
		InterfaceFieldInfo interfaceFieldInfo_tmp = projectInfo.getInterfaceFieldInfoByChannelFieldId();
		Object channelId = ParamsUtil.getValue(interfaceFieldInfo_tmp, reqContext);
		if(channelId == null){
			return null;
		}
		ChannelInfo channelInfo=channelInfoDao.getOne(channelId.toString());
		if(channelInfo == null){
			return null;
		}
		reqContext.setChannelInfo(channelInfo);
		infoFlow.setChannelId(channelInfo.getChannelId());
		return channelInfo;
	}
	private InterfaceInfo getInterfaceInfo(ReqContext reqContext ,ReqInfoFlow infoFlow,ProjectInfo projectInfo){
		InterfaceFieldInfo interfaceFieldInfo_tmp = projectInfo.getInterfaceFieldInfoByInterfaceIdentificationFieldId();
		//通过接口标识字段获取接口信息
		InterfaceInfo interfaceInfo=new InterfaceInfo();
		interfaceInfo.setInterfaceName(ParamsUtil.getValue(interfaceFieldInfo_tmp, reqContext).toString());
		interfaceInfo.setProjectInfo(new ProjectInfo(projectInfo.getProjectId()));
		List<InterfaceInfo> listTmp = interfaceInfoDao.findAll(Example.of(interfaceInfo));
		if(listTmp .size() == 0){
			return null;
		}
		interfaceInfo=listTmp.get(0);
		reqContext.setInterfaceInfo(interfaceInfo);
		infoFlow.setInterfaceId(interfaceInfo.getInterfaceId());
		infoFlow.setModularId(interfaceInfo.getModularInfo().getModularId());
		return interfaceInfo;
		
	}
	private void setReqParams(ReqContext reqContext ,ReqInfoFlow infoFlow,ProjectInfo projectInfo){
		REQ_PARAMS_POSITION params_POSITION=(REQ_PARAMS_POSITION) BeanUtil.getBean(projectInfo.getInterfaceReqParamsPosition());
		reqContext.setReqParams(params_POSITION.getParams(reqContext));
		infoFlow.setReqBody(JSON.toJSONString(reqContext));
	}
	private void saveInterfaceCase(ReqContext reqContext){
		reqContext.getInterfaceInfo().getInterfaceCaseInfos().stream()
		.forEach((ici) -> {
			Object value=ParamsUtil.getValue(ici.getInterfaceFieldInfo(), reqContext);
			if(value == null){
				return;
			}
			boolean isCase = Arrays.asList(ici.getInterfaceFieldValue().split("@fzh@")).contains(value);
			ChannelInterfaceCaseId id = new ChannelInterfaceCaseId(reqContext.getChannelInfo().getChannelId(), ici.getInterfaceCaseId());
			ChannelInterfaceCase channelInterfaceCase = null;
			if(channelInterfaceCaseDao.exists(id)){
				channelInterfaceCase = channelInterfaceCaseDao.getOne(id);
			}else{
				channelInterfaceCase=new ChannelInterfaceCase(id,
						reqContext.getInterfaceInfo().getModularInfo(), reqContext.getInterfaceInfo(), 
						reqContext.getChannelInfo(), 
						ici, reqContext.getProjectInfo());
				channelInterfaceCase.setInterfaceCaseState(0);
			}
			if(channelInterfaceCase.getInterfaceCaseState() == 1){
				return;
			}
			channelInterfaceCase.setInterfaceCaseState(isCase?1:0);
			channelInterfaceCaseDao.save(channelInterfaceCase);
		});
	}
	public void saveChannelInterface(ReqInfoFlow infoFlow,ReqContext reqContext){
		ChannelInterface channelInterface=null;
		ChannelInterfaceId id= new ChannelInterfaceId(infoFlow.getChannelId(), infoFlow.getInterfaceId());
		if(channelInterfaceDao.exists(id)){
			channelInterface=channelInterfaceDao.getOne(id);
		}else{
			channelInterface=new ChannelInterface(new ChannelInterfaceId(infoFlow.getChannelId(), infoFlow.getInterfaceId()), 
					reqContext.getInterfaceInfo().getModularInfo(), reqContext.getInterfaceInfo(), 
					reqContext.getChannelInfo(), 
					reqContext.getProjectInfo());
			channelInterface.setState(0);
		}
		if(channelInterface.getState() != 2){
			if(infoFlow.getReqSuccess()){
				channelInterface.setState(2);					
			}else{
				channelInterface.setState(1);
			}	
		}
		channelInterfaceDao.save(channelInterface);
	}
	
	private void saveInfoFlow(ReqInfoFlow infoFlow,ReqContext reqContext,ProjectInfo projectInfo){
		infoFlow.setResTimestamp(new Timestamp(System.currentTimeMillis()));
		infoFlow.setResBody(JSON.toJSONString(reqContext));
		Object code=ParamsUtil.getValue(projectInfo.getInterfaceFieldInfoByInterfaceCodeFieldId(), reqContext);
		infoFlow.setResCode( code != null ? code.toString() : null);
		Object message=ParamsUtil.getValue(projectInfo.getInterfaceFieldInfoByInterfaceMessageFieldId(), reqContext);
		infoFlow.setResMessage( message != null ? message.toString() : null);
		infoFlow.setReqSuccess(Arrays.asList(projectInfo.getInterfaceSuccessCodeValue().split("@fzh@")).contains(infoFlow.getResCode()));
		reqInfoFlowDao.save(infoFlow);
	}
	
	private void setResponse(HttpServletResponse response,ReqContext reqContext){
		reqContext.getHttpResHead().keySet()
		.stream()
		.forEach((k)->{
			response.setHeader(k, reqContext.getHttpResHead().getString(k));
		});
		RES_PARAMS_POSITION params_POSITION=(RES_PARAMS_POSITION) BeanUtil.getBean(reqContext.getProjectInfo().getInterfaceResParamsPosition());
		params_POSITION.outPutRes(reqContext);
	}
	private Optional<ProjectInfo> getProjectInfo(HttpServletRequest request,HttpServletResponse response,String projectServletPath){
		@SuppressWarnings("unchecked")
		Optional<ProjectInfo> tmp=((List<ProjectInfo>)cacheManager.getValue(projectInfoItem).getValue())
				.stream()
				.filter(p -> {
					return pathMatcher.match(p.getContextPath(), projectServletPath);
				})
				.findAny();
		return tmp;
	}
	private ReqContext createReqContext(HttpServletRequest request,HttpServletResponse response,ProjectInfo projectInfo,String projectServletPath){
		ReqContext reqContext=new ReqContext();
		reqContext.setIp(request.getRemoteAddr());
		reqContext.setRequest(request);
		reqContext.setResponse(response);
		reqContext.setProjectInfo(projectInfo);
		reqContext.setUrlParams(projectServletPath.split("/"));
		reqContext.setResParams(new JSONObject());
		reqContext.setHttpResHead(new JSONObject());
		return reqContext;
	}
	private void setRequestParams(ReqContext reqContext,HttpServletRequest request){
		JSONObject requestParams=new JSONObject();
		request.getParameterMap().keySet()
		.stream()
		.forEach(k -> {
			requestParams.put(k, request.getParameter(k));
		});
		reqContext.setHttpReqParams(requestParams);
	}
	private void setRequestHead(ReqContext reqContext,HttpServletRequest request){
		JSONObject requestHead=new JSONObject();
		Enumeration<String> enumeration=request.getHeaderNames();
		while(enumeration.hasMoreElements()){
			String k = enumeration.nextElement();
			requestHead.put(k, request.getHeader(k));
		}
		reqContext.setHttpReqHead(requestHead);
	}
}
